﻿#if NET_40_OR_GREATER

using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Text;
using System.Web;
using System.Web.Security;
using OpenWaves.ImageTransformations;

namespace OpenWaves.Web.ImageTransformations
{
    public class WebImageTransformationModuleImplementation : IWebImageTransformationService, IWebImageTransformationModuleImplementation
    {
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline")]
        static WebImageTransformationModuleImplementation()
        {
            //
            // HACK: This dummy call is needed because MachineKey.Decode throws NullReferenceException
            // when called before MachineKey.Encode was called.
            // 
            MachineKey.Encode(new byte[0], MachineKeyProtection.Validation);
        }

        private readonly IWebImageTransformationService webImageTransformationService;

        public WebImageTransformationModuleImplementation(IWebImageTransformationService webImageTransformationService)
        {
            Contract.Requires<ArgumentNullException>(webImageTransformationService != null);

            this.webImageTransformationService = webImageTransformationService;
        }

        public Url GetTransformedImageUrl(Url imageUrl, IImageTransformation transformation)
        {
            if (imageUrl is RelativeUrl)
            {
                var builder = imageUrl.GetBuilder();
                EncodeTransformationInUrl(transformation, builder);
                return builder.ToUrl();
            }
            else
            {
                return this.webImageTransformationService.GetTransformedImageUrl(imageUrl, transformation);
            }            
        }

        public bool PostAuthorizeRequest(HttpContextBase context)
        {
            var url = context.Request.GetRawRelativeUrl();

            var transformation = DecodeTransformationFromUrl(url);
            if (transformation == null)
                return false;

            var transformedImageUrl = this.webImageTransformationService.GetTransformedImageUrl(
                url.Path.ToUrl(), 
                transformation);

            context.RewritePath(transformedImageUrl.ToString());
            context.Response.Cache.SetExpires(Time.Now + TimeSpan.FromDays(365));
            return true;
        }

        private static void EncodeTransformationInUrl(IImageTransformation transformation, UrlBuilder urlBuilder)
        {
            var bytes = Encoding.ASCII.GetBytes(transformation.ToString());
            urlBuilder.AddParameter("t", MachineKey.Encode(bytes, MachineKeyProtection.Validation));
        }

        private static IImageTransformation DecodeTransformationFromUrl(Url url)
        {
            string encodedTransformationString;
            if (url.Query.TryGetValue("t", out encodedTransformationString) == false)
                return null;

            if (String.IsNullOrEmpty(encodedTransformationString))
                return null;

            byte[] x;
            try
            {
                x = MachineKey.Decode(encodedTransformationString, MachineKeyProtection.Validation);
            }
            catch (ArgumentException)
            {
                //
                // MachineKey.Decode throws ArgumentException when it does not like encodedData. 
                // There is no sensible (without knowing the impl details of Decode) way we can validate the data before passing it to Decode.
                //
                return null;
            }

            if (x == null)
                return null;

            var transformationString = Encoding.ASCII.GetString(x);

            IImageTransformation transformation;
            if (ImageTransformation.TryParse(transformationString, out transformation) == false)
            {
                Log.Warning("Failed to parse transformation string: {0}", transformationString);
                return null;
            }

            return transformation;
        }
    }
}

#endif